/*
 * Copyright (c) 2021, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "virtnet.h"
#include "virtnet_rpc.h"
#include "virtnet_controller.h"

#define CONFIG_FILE	"/opt/mellanox/mlnx_virtnet/virtnet.conf"

/* For signal handler as it doesn't allow argument carry over */
struct virtnet_context *ctx = NULL;
struct virtnet_rpc_context rpc_ctx = {};
bool quit = false;

const char *argp_program_version = "v1.1.9";

int virtnet_config_load(const char *file_path,
			struct virtnet_init_attr *config)
{
	char *buffer = NULL;
	FILE *fp = NULL;
	long lSize = 0;
	int ret = 0;

	fp = fopen(file_path, "rb");
	if (!fp) {
		log_info("No config file found, use default values");
		goto out;
	}

	fseek(fp, 0L, SEEK_END);
	lSize = ftell(fp);
	rewind(fp);

	/* allocate memory for entire content */
	buffer = calloc(1, lSize + 1);
	if (!buffer) {
		ret = ENOMEM;
		log_debug("Memory allocation failed");
		goto out_close;
	}

	/* copy the file into the buffer */
	if (1 != fread(buffer, lSize, 1, fp)) {
		ret = errno;
		log_debug("Read config file failed");
		goto out_free;
	}

	cJSON *jsoncfg = cJSON_Parse(buffer);
	if (!jsoncfg) {
		ret = EINVAL;
		log_error("Wrong config format");
		goto out_free;
	}

	cJSON *mlnx_device_str = cJSON_GetObjectItem(jsoncfg, "ib_dev_for_static_pf");
	if (mlnx_device_str)
		snprintf(config->ib_dev_name, CONFIG_STR_SIZE, "%s",
			 mlnx_device_str->valuestring);

	cJSON *is_lag = cJSON_GetObjectItem(jsoncfg, "is_lag");
	if (is_lag)
		config->is_lag = !!is_lag->valueint;

	cJSON *pf_mac = cJSON_GetObjectItem(jsoncfg, "pf_mac");
	if (pf_mac) {
		char *str = pf_mac->valuestring;
		uint8_t *mac = (uint8_t *)&config->pf_mac;
		if (strlen(str) != 17)
			goto out_free;
		sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
			     &mac[0], &mac[1], &mac[2],
			     &mac[3], &mac[4], &mac[5]);
	}

out_free:
	free(buffer);
out_close:
	fclose(fp);
out:
	return ret;
}

void virtnet_cleanup(struct virtnet_context *virtnet_ctx)
{
	struct virtnet_device *dev;
	struct snap_context *sctx;
	int i;

	log_debug("Cleaning up");

	if (!virtnet_ctx)
		return;

	sctx = virtnet_ctx->sctx;

	if (!sctx)
		return;

	virtnet_rpc_stop(&rpc_ctx);

	for (i = 0; i < sctx->virtio_net_pfs.max_pfs; i++) {
		dev = &virtnet_ctx->dev_list[i];

		if (!dev || !dev->snap_pci)
			continue;

		virtnet_device_close(dev);
		virtnet_sf_destroy(dev);
	}

	virtnet_destroy(virtnet_ctx);
}

int main(int argc, char **argv)
{
	struct virtnet_init_attr attr = {
		.ib_dev_name = "mlx5_0",
	};
	int ret = 0;

	virtnet_signal_handler_install();

	virtnet_log_open();

	log_info("Start virtio_net_controller %s", argp_program_version);

	if (virtnet_config_load(CONFIG_FILE, &attr)){
		log_error("Failed to load user config file");
		goto out;
	}

	log_info("Config: static PF dev(%s), is_lag(%d), "
		 "pf_mac(%02X:%02X:%02X:%02X:%02X:%02X)",
		 attr.ib_dev_name, attr.is_lag,
		 (uint8_t)((attr.pf_mac >> 40) & 0xff),
		 (uint8_t)((attr.pf_mac >> 32) & 0xff),
		 (uint8_t)((attr.pf_mac >> 24) & 0xff),
		 (uint8_t)((attr.pf_mac >> 16) & 0xff),
		 (uint8_t)((attr.pf_mac >> 8) & 0xff),
		 (uint8_t)(attr.pf_mac & 0xff));

	ctx = virtnet_init(&attr, NULL);
	if (!ctx) {
		log_error("Failed to initialize virtnet");
		goto out;
	}

	virtnet_rpc_start(&rpc_ctx, ctx);

	while (!quit) {
		if ((ret = virtnet_epoll_wait(1)))
			break;
		/* Sleep to yield CPU */
		usleep(100);
	}

	virtnet_cleanup(ctx);
out:
	log_info("Stop virtio_net_controller");
	virtnet_log_close();
	virtnet_signal_handler_uninstall();

	return ret;
}
